/*-
 * Copyright (C) 2018-2020, Shanghai Trusted Industry Control Platform Co., Ltd.
 * Created by 周正辉 (Zhenghui Zhou) <zhouzhenghui@ticpsh.com>, 赵楷文 (Kaiwen
 * Zhao) <zhaokaiwen@ticpsh.com>
 */

#include <clang/Tooling/CommonOptionsParser.h>
#include <llvm/Support/Signals.h>
#include <llvm/Support/TargetSelect.h>

#include <fstream>
#include <iomanip>
#include <iostream>

#include "libtestif.h"
#include "libtestud.h"
#include "share/analyze_setting.h"

#ifdef _WIN32
#include <direct.h>
#include <stdlib.h>
#define chdir _chdir
#else
#include <unistd.h>
#endif

using namespace llvm;
using namespace clang::tooling;
using namespace rocket::testif;
using njson = nlohmann::json;

/**
 * @brief 命令行参数
 */
static llvm::cl::OptionCategory opts("Test interface analyzer");

/**
 * @brief 目标文件路径
 */
static llvm::cl::opt<std::string> homedir(
    "H", llvm::cl::desc("Specify the root directory of source tree"),
    llvm::cl::cat(opts));

/**
 * @brief 报文输出文件路径
 */
static llvm::cl::opt<std::string> output(
    "o",
    llvm::cl::desc("Specify the output (test interfaces data in json format) "
                   "file instead of standard output"),
    llvm::cl::cat(opts));

/**
 * @brief 文件依赖映射报文
 */
static llvm::cl::opt<bool> denpendency(
    "M", llvm::cl::desc(
             "Output the dependencies of the main source file in json format"));

/**
 * @brief 用户定义模板特化
 */
static llvm::cl::opt<bool> udspec(
    "U",
    llvm::cl::desc("Get user defined template specializations in json format "
                   "and speclize the templates"),
    llvm::cl::cat(opts));
static llvm::cl::list<std::string> udspec_select(
    "s",
    llvm::cl::desc(
        "Select the interface appeared caused by the user defined "
        "specializations by specid, defult only select the last specialize "
        "action, use \"=all\" to get whole testif after specialization"),
    cl::ZeroOrMore, llvm::cl::cat(opts));

static llvm::cl::list<std::string> excluded_namespace(
    "exclude-namespace",
    llvm::cl::desc("All symbols in this namespace will be integrated "
                   "during the stub actions."),
    llvm::cl::cat(opts));

static llvm::cl::opt<int> analyze_level(
    "analyze-level",
    llvm::cl::desc("All symbols in this namespace will be integrated "
                   "during the stub actions."),
    llvm::cl::cat(opts));

static llvm::cl::opt<bool> constexpr_is_stub(
    "constexpr-is-stub",
    llvm::cl::desc("should constexpr functions be treated as stub-functions"),
    llvm::cl::cat(opts));

static llvm::cl::opt<bool> retcopy_is_stub(
    "retcopy-is-stub",
    llvm::cl::desc("should copy cotr of stub return as stub-functions"),
    llvm::cl::cat(opts));

static llvm::cl::opt<bool> analyze_line_statistic(
    "analyze-line-statistic",
    llvm::cl::desc("should constexpr functions be treated as stub-functions"),
    llvm::cl::cat(opts));

static llvm::cl::opt<bool> analyze_local_vars(
    "analyze-local-vars",
    llvm::cl::desc("should analyze local variables in function"),
    llvm::cl::cat(opts));

static llvm::cl::opt<bool> analyze_macro_expansions(
    "analyze-macro-expansions",
    llvm::cl::desc("should analyze macro expansions in file"),
    llvm::cl::cat(opts));

static llvm::cl::opt<bool> analyze_indirect_stubs(
    "analyze-indirect-stubs", llvm::cl::desc("should analyze indirect stubs"),
    llvm::cl::cat(opts));

static llvm::cl::opt<bool> analyze_fixedaddr_offsets(
    "analyze-fixedaddr-offsets",
    llvm::cl::desc("should analyze fixedaddr offsets"), llvm::cl::cat(opts));

static llvm::cl::opt<bool> analyze_locvar_size(
    "analyze-locvar-size", llvm::cl::desc("should analyze local variable size"),
    llvm::cl::cat(opts));

static llvm::cl::opt<std::string> debug_result(
    "debug-result", llvm::cl::desc("print result json to path"),
    llvm::cl::cat(opts));

int main(int argc, const char** argv) {
  llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);

  // Initialize targets for clang module support.
  llvm::InitializeAllTargets();
  llvm::InitializeAllTargetMCs();
  llvm::InitializeAllAsmPrinters();
  llvm::InitializeAllAsmParsers();

#if LLVM_VERSION_MAJOR < 13
  CommonOptionsParser op(argc, argv, opts);
#else
  auto cop = CommonOptionsParser::create(argc, argv, opts);
  if (!cop) {
    llvm::errs() << "ERROR: failed to parse command-line arguments. "
                 << llvm::toString(cop.takeError()) << "\n";
    return 1;
  }
  auto& op = cop.get();
#endif

  if (homedir.getNumOccurrences()) {
    int ret = chdir(homedir.getValue().c_str());
    if (ret) {
      perror("the root directory of source tree not exist");
      return ret;
    }
  }

  const std::vector<std::string>& paths = op.getSourcePathList();
  std::vector<std::string> canonical_paths;
  for (auto& path : paths) {
    canonical_paths.push_back(boost::filesystem::canonical(path).string());
  }

  rocket::share::RocketAnalyzeSetting setting;
  setting.addIntegratedNamespace(excluded_namespace);
  if (analyze_level.getNumOccurrences()) {
    switch (analyze_level) {
      case 0:
        setting.analyze_level =
            rocket::share::RocketAnalyzeSetting::AnalyzeLevel::AL_Local;
        break;
      case 1:
        setting.analyze_level =
            rocket::share::RocketAnalyzeSetting::AnalyzeLevel::AL_Used;
        break;
      case 2:
        setting.analyze_level =
            rocket::share::RocketAnalyzeSetting::AnalyzeLevel::AL_Full;
        break;
    }
  }

  if (constexpr_is_stub.getNumOccurrences()) {
    setting.constexpr_is_stub = constexpr_is_stub;
  }

  if (retcopy_is_stub.getNumOccurrences()) {
    setting.retcopy_is_stub = retcopy_is_stub;
  }

  if (analyze_line_statistic.getNumOccurrences()) {
    setting.analyze_line_statistic = analyze_line_statistic;
  }

  if (analyze_local_vars.getNumOccurrences()) {
    setting.analyze_local_vars = analyze_local_vars;
  }

  if (analyze_macro_expansions.getNumOccurrences()) {
    setting.analyze_macro_expansions = analyze_macro_expansions;
  }

  if (analyze_indirect_stubs.getNumOccurrences()) {
    setting.analyze_indirect_stubs = analyze_indirect_stubs;
  }

  if (analyze_fixedaddr_offsets.getNumOccurrences()) {
    setting.analyze_fixedaddr_offsets = analyze_fixedaddr_offsets;
  }

  if (analyze_locvar_size.getNumOccurrences()) {
    setting.analyze_locvar_size = analyze_locvar_size;
  }

  if (debug_result.getNumOccurrences()) {
    setting.debug_result = debug_result;
  }

  njson file_ifs;
  if (denpendency) {
    // 解析include文件依赖
    file_ifs =
        parse_dependencies(op.getCompilations(), canonical_paths, setting);
  } else if (udspec) {
    // -U 使用原文件生成的if报文
    file_ifs =
        parse_test_interfaces(op.getCompilations(), canonical_paths, setting);

    njson udspecs;
    try {
      std::cin >> udspecs;
    } catch (nlohmann::json::exception& e) {
      std::cerr << "bad udspecs json:" << e.what() << std::endl;
    }

    parse_udtype_interfaces(op.getCompilations(), canonical_paths, file_ifs,
                            udspecs, udspec_select, setting);
  } else {
    // 解析新的if报文
    file_ifs =
        parse_test_interfaces(op.getCompilations(), canonical_paths, setting);
  }

  std::ofstream of;
  if (!setting.debug_result.empty()) {
    try {
      of.open(setting.debug_result);
      if (of.is_open()) {
        of << file_ifs.dump(4, ' ', false, njson::error_handler_t::replace);
      }
    } catch (...) {
    }
  }

  if (output.getNumOccurrences()) {
    std::ofstream os(output.getValue());
    os << file_ifs.dump(4, ' ', false, njson::error_handler_t::replace);
  } else {
    std::cout << file_ifs.dump(4, ' ', false, njson::error_handler_t::replace)
              << std::endl;
  }

  return 0;
}
